Mapper Container topic
MapperContainer
s are the backbone of dart_mappable
. A MapperContainer
s job is to lookup the correct
mapper for a type and call its respective method.
To find the mapper for a given type, the container first looks at its own set of mappers and when there is no match it refers to its linked containers.
Working with MapperContainer
s
This is an advanced topic. Usually you don't need to worry about containers, as they are hidden from the user when doing standard
things like decoding or encoding from json or using the generated mixins toString
, ==
or hashCode
overrides.
You don't need to know this for most use-cases. But feel free to read on if you are interested.
However each generated mapper internally defines and uses its own MapperContainer
, which you can access using
the <ClassName>Mapper.container
getter.
When you look at the interface of a mapper, you can see all the respective methods for serialization and mapping,
like fromJson
, toJson
, isEqual
, hash
and asString
. You can use these methods with any object as long
as the container can resolve a mapper for it.
To control which types a MapperContainer
can resolve see the following methods:
-
use<T>(MapperBase<T> mapper)
will add a new mapper for typeT
. -
unuse<T>()
will remove the current mapper for typeT
. Note that this works for any mapper, custom, generated or for primitive types. -
useAll(Iterable<MapperBase> mapper)
works asuse()
but for a list of mappers. -
get<T>()
will return the current mapper for typeT
. -
getAll()
will return all currently registered mappers.
Additionally to controlling mappers manually for a single container, you can also link a container to another. That way container A can use all mappers of container B implicitly.
link(MapperContainer container)
links the provided container to the current one.
The following example shows this in action for a schematic setup of two classes and mappers.
// Suppose we have two classes and their respective mappers
class A {}
class B {}
class AMapper extends MapperBase<A> {} // generated
class BMapper extends MapperBase<B> {} // generated
void main() {
// succeeds - generated mappers have their own container
AMapper.container.toJson(A());
// fails - generated mappers only work with their respective types
BMapper.container.toJson(A());
// creates a new empty container
var containerX = MapperContainer();
// fails - no mappers were added yet
containerX.toJson(A());
// succeeds - added the respective mapper
containerX.use(AMapper());
containerX.toJson(A());
var containerY = MapperContainer();
containerY.use(BMapper());
// succeeds - added the respective mapper
containerY.toJson(B());
// fails - missing mapper for type A
containerY.toJson(A());
// links containerX to containerY
containerY.link(containerX);
// succeeds - mapper is resolved through linked containerX
containerY.toJson(A());
// fails - linking is one-directional
containerX.toJson(B());
}
Default Container
There is a special global default container that can be accessed using MapperContainer.defaults
. This
container initially contains mappers for all primitive and core types. Types included are:
dynamic
, Object
, String
, int
, double
, num
, bool
, DateTime
, List
, Set
, Map
All other containers are implicitly linked to this default container.
You can use this container to add mappers for types, that are thereby supported by all other containers.
// suppose we have a class and a custom mapper for it
class MyClass {}
class MyClassMapper extends SimpleMapper<MyClass> { ... }
void main() {
var container = MapperContainer();
// this doen't work, since the mapper for type [MyClass] wasn't added yet to this container
container.toJson(MyClass());
// we add the mapper as a global default
MapperContainer.defaults.use(MyClassMapper());
// now this works, since all containers are linked to the default container
container.toJson(MyClass());
}
Combined Containers
For convenience or when using generic decoding you may want to have a container that knows
all classes in your project. Such a container can be auto-generated using the
@MappableLib(createCombinedContainer: true)
annotation.
This feature is documented here.
Encoding Lists, Sets and Maps
Because all of the MapperContainer
s methods are generic, you are not limited to use a single
object with these methods. dart_mappable
also support List
s, Set
s and Map
s out of the box,
without any special syntax, workarounds or hacks.
@MappableClass()
class Dog with DogMappable {
String name;
Dog(this.name);
}
@MappableClass()
class Box<T> with BoxMappable<T> {
T content;
Box(this.content);
}
void main() {
// Case 1: Simple list.
// We use the default container since we only use core types.
List<int> nums = MapperContainer.defaults.fromJson('[2, 4, 105]');
print(nums); // [2, 4, 105]
// Case 2: Set of objects.
// We use the generated container for [Dog], but with a set of objects.
Set<Dog> dogs = DogMapper.container.fromJson('[{"name": "Thor"}, {"name": "Lasse"}, {"name": "Thor"}]');
print(dogs); // {Dog(name: Thor), Dog(name: Lasse)}
// Case 3: More complex lists, like generics.
// We use the generated container for [Box], but with a list of generic objects.
List<Box<double>> boxes = BoxMapper.container.fromJson('[{"content": 0.1}, {"content": 12.34}]');
print(boxes); // [Box(content: 0.1), Box(content: 12.34)]
}
There is also the MapperContainer.fromIterable
method. This can be used if you already have a list of dynamic objects instead of the raw json string.
Additionally this can get handy to decode a dynamic list of partly-encoded values:
List<double> myNumbers = MapperContainer.defaults.fromIterable([2.312, '1.32', 500, '1e4']);
print(myNumbers); // [2.312, 1.32, 500.0, 10000.0]
Non-Trivial Maps
We also support decoding of non-trivial maps. Although the use-cases might be rare, you can decode to something other than a map of string keys like this:
var encodedMap = {
{'name': 'Bonny'}: 1,
{'name': 'Clyde'}: 5,
};
Map<Dog, int> treatsPerDog = DogMapper.container.fromValue(encodedMap);
print(treatsPerDog[Dog('Clyde')]!); // 5
var myMap = DogMapper.container.toValue(treatsPerDog);
print(myMap); // {{name: Bonny}: 1, {name: Clyde}: 5}
Note: Make sure to add @MappableClass
on your key class, in order to enable easy property access.
Note: Since json only supports string keys, we can't do fromJson
or toJson
on these maps.
You would have to decode / encode your keys and values separately.
🎉 Congrats, you followed the tour until the end. Now you know everything about this package.
Classes
Exceptions / Errors
- MapperException Mapper Container
- General exception class used throughout the package